using System;
using System.Linq;
using System.Collections.Generic;
using System.Threading;
using NHibernate;
using NHibernate.Linq;
using FluentNHibernate.Automapping;
using NHibernate.Tool.hbm2ddl;
using NHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using Kollektiv.DB;
using Kollektiv.Config;
using FluentNHibernate.Cfg;
using System.Runtime.Serialization;
using Kollektiv.Extension;
using System.Reflection;
using System.Diagnostics;
using System.IO;

namespace Kollektiv.DB.NHibernate
{	
	public enum DatabaseType : int
	{
		MySQL = 0,
		PostgreSQL = 1
	}
	
	/// <summary>
	/// Service for the persisting data objects via FluentNHibernate.
	/// </summary>
	public class NHibernateManager : DataManager
	{
		public const string _CONFIG_DBTYPE = "DBTYPE";
		public const string _CONFIG_HOST = "HOST";
		public const string _CONFIG_PORT = "PORT";
		public const string _CONFIG_DB = "DB";
		public const string _CONFIG_USERNAME = "USERNAME";
		public const string _CONFIG_PASSWORD = "PASSWORD";
		public const string _CONFIG_ASSEMBLIES = "ASSEMBLIES";
		private ISession session;
		
		public override bool ActiveTransaction {
			get {
				return this.session != null && this.session.Transaction != null && this.session.Transaction.IsActive;
			}
		}
		
		public NHibernateManager ()
		{
		}
		
		protected override void onInitialize (bool create)
		{
			this.BuildSession (create);
			
			base.onInitialize (create);
		}
		
		private void BuildSession (bool expose)
		{
			FluentConfiguration config = Fluently.Configure ()
				.Database (this.GetConfigurer ())
				.Mappings (m => this.AddMappings (m));
			
			if (expose)
				config = config.ExposeConfiguration (c => this.BuildSchema (c));
			
			this.session = config.BuildSessionFactory ().OpenSession ();
		}
		
		private void AddMappings (MappingConfiguration m)
		{
			foreach (string assembly in this.GetConfigValue(_CONFIG_ASSEMBLIES).Split(';')) {
				m.FluentMappings.AddFromAssembly (assembly.Trim ().FindAssembly ());
			}
		}
		
		private IPersistenceConfigurer GetConfigurer ()
		{
			IPersistenceConfigurer configurer = null;
			
			DatabaseType dbtype = (DatabaseType)Enum.Parse (typeof(DatabaseType), this.GetConfigValue (_CONFIG_DBTYPE));
			switch (dbtype) {
			case DatabaseType.MySQL:
				configurer = MySQLConfiguration.Standard.ConnectionString (c => c
					.Server (this.GetConfigValue (_CONFIG_HOST))
					.Database (this.GetConfigValue (_CONFIG_DB))
					.Username (this.GetConfigValue (_CONFIG_USERNAME))
					.Password (this.GetConfigValue (_CONFIG_PASSWORD)));
				break;
			case DatabaseType.PostgreSQL:
				configurer = PostgreSQLConfiguration.PostgreSQL82.ConnectionString (c => c
						.Host (this.GetConfigValue (_CONFIG_HOST))
						.Port (Convert.ToInt32 (this.GetConfigValue (_CONFIG_PORT)))
						.Database (this.GetConfigValue (_CONFIG_DB))
						.Username (this.GetConfigValue (_CONFIG_USERNAME))
						.Password (this.GetConfigValue (_CONFIG_PASSWORD)));
				break;
			}
			
			return configurer;
		}
		
		private void BuildSchema (Configuration config)
		{
			new SchemaExport (config).Create (false, true);
		}
		
		public override void Dispose ()
		{
			if (this.session.Transaction != null && this.session.Transaction.IsActive)
				this.RollbackTransaction ();
			
			this.session.Close ();
		}
		
		public override void BeginTransaction ()
		{
			this.session.BeginTransaction ();
		}
		
		public override void CommitTransaction ()
		{
			if (this.session.Transaction != null && this.session.Transaction.IsActive) {
				this.session.Transaction.Commit ();
			}
		}
		
		public override void RollbackTransaction ()
		{
			if (this.session.Transaction != null && this.session.Transaction.IsActive) {
				this.session.Transaction.Rollback ();
			}
		}
		
		public override void SaveOrUpdate (DataObject obj)
		{
			this.session.SaveOrUpdate (obj);
		}
		
		public override void Delete (DataObject obj)
		{
			this.session.Delete (obj);
		}
		
		public override IQueryable<T> Get<T> ()
		{
			return this.session.Query<T> ();
		}
	}
}

